package dgopenai

import (
	"context"
	"errors"
	"fmt"
	"io"
	"strings"
	"time"

	dgctx "github.com/darwinOrg/go-common/context"
	dgerr "github.com/darwinOrg/go-common/enums/error"
	"github.com/darwinOrg/go-common/utils"
	dglogger "github.com/darwinOrg/go-logger"
	"github.com/sashabaranov/go-openai"
)

// GenerateContent generates content using a template
func GenerateContent(ctx *dgctx.DgContext, tgcReq *TemplateGenerateContentRequest) (*GenerateContentResponse, error) {
	templateName, gcReq, err := getGenerateContentRequest(ctx, tgcReq)
	if err != nil {
		return nil, err
	}

	var (
		gcResp *GenerateContentResponse
		retry  int
		cost   time.Duration
	)

	defer func() {
		go func() {
			_ = MyAigcResponseProvider.SaveGenerateContentResponse(ctx, templateName, gcReq, gcResp, tgcReq.BizParams, retry, cost, err)
		}()
	}()

	for i := 0; i < gcReq.TryTimes; i++ {
		for j, platformModel := range gcReq.PlatformModels {
			retry = i*len(gcReq.PlatformModels) + j
			gcResp, cost, err = createChatCompletion(ctx, aigcKey, templateName, platformModel, gcReq, tgcReq.BizParams)
			if err != nil {
				continue
			}

			return gcResp, nil
		}
	}

	return nil, err
}

// StreamGenerateContent generates content using a template and streams the response
func StreamGenerateContent(ctx *dgctx.DgContext, tgcReq *TemplateGenerateContentRequest) (chan *GenerateContentResponse, error) {
	templateName, gcReq, err := getGenerateContentRequest(ctx, tgcReq)
	if err != nil {
		return nil, err
	}

	var (
		mergedResp *GenerateContentResponse
		retry      int
		cost       time.Duration
	)

	for i := 0; i < gcReq.TryTimes; i++ {
		for j, platformModel := range gcReq.PlatformModels {
			retry = i*len(gcReq.PlatformModels) + j
			var (
				stream    *openai.ChatCompletionStream
				logParams []any
			)
			stream, logParams, err = createChatCompletionStream(ctx, aigcKey, templateName, platformModel, gcReq, tgcReq.BizParams)
			if err != nil {
				continue
			}

			respChan := make(chan *GenerateContentResponse, 10)
			go func() {
				defer func() {
					_ = stream.Close()
					close(respChan)
					_ = MyAigcResponseProvider.SaveGenerateContentResponse(ctx, templateName, gcReq, mergedResp, tgcReq.BizParams, retry, cost, err)
				}()

				mergedResp, cost, err = receiveChatCompletionStream(ctx, aigcKey, stream, respChan, platformModel, gcReq, tgcReq.BizParams, logParams)
			}()

			return respChan, nil
		}
	}

	return nil, err
}

// GenerateJsonObject generates content using a template and parses it as a JSON object
func GenerateJsonObject[T any](ctx *dgctx.DgContext, tgcReq *TemplateGenerateContentRequest) (*T, error) {
	resp, err := GenerateContent(ctx, tgcReq)
	if err != nil {
		return nil, err
	}

	t, err := utils.ConvertJsonStringToBean[T](resp.Content)
	if err != nil {
		return nil, err
	}

	return t, nil
}

// createChatCompletion creates a chat completion
func createChatCompletion(ctx *dgctx.DgContext, gcType string, templateName string, platformModel *PlatformModelPair, gcReq *GenerateContentRequest, bizParams map[string]any) (*GenerateContentResponse, time.Duration, error) {
	start := time.Now()
	platform := platformModel.Platform
	model := platformModel.Model
	logParams := []any{templateNameKey, templateName, platformKey, platform, modelKey, model}
	if len(bizParams) > 0 {
		for k, v := range bizParams {
			logParams = append(logParams, k, v)
		}
	}
	dglogger.Infow(ctx, fmt.Sprintf("start %s", gcType), logParams...)
	client := platformClientMap[platform]
	ccReq := buildChatCompletionRequest(gcReq)
	ccReq.Model = model

	resp, err := client.CreateChatCompletion(context.Background(), ccReq)
	if err != nil {
		dglogger.Errorw(ctx, fmt.Sprintf("%s error: %v", gcType, err), logParams...)
		if strings.Contains(err.Error(), "rate limit") {
			randomSeconds := utils.RandomIntInRange(5, 15)
			dglogger.Warnw(ctx, fmt.Sprintf("%s rate limit, sleepSeconds: %v", gcType, randomSeconds), logParams...)
			time.Sleep(time.Duration(randomSeconds) * time.Second)
		}
		return nil, 0, err
	}

	if len(resp.Choices) == 0 || resp.Choices[0].Message.Content == "" {
		dglogger.Warnw(ctx, fmt.Sprintf("%s empty", gcType), logParams...)
		return nil, 0, dgerr.SYSTEM_ERROR
	}

	gcResp := buildGenerateContentResponse(gcReq, &resp, platformModel)
	if gcResp.Content == "" && gcResp.FunctionCall == nil {
		dglogger.Warnw(ctx, fmt.Sprintf("%s response empty", gcType), logParams...)
		return gcResp, 0, dgerr.SYSTEM_ERROR
	}

	if gcResp.FunctionCall != nil && MyFunctionCallProvider != nil {
		gcResp.Content, err = MyFunctionCallProvider.Call(ctx, gcResp.FunctionCall, bizParams)
		if err != nil {
			dglogger.Errorf(ctx, fmt.Sprintf("%s function call error: %v", gcType, err), logParams...)
			return gcResp, 0, err
		}
	}

	cost := time.Since(start)
	dglogger.Infow(ctx, fmt.Sprintf("%s success, cost: %v", gcType, cost), logParams...)

	return gcResp, cost, nil
}

// createChatCompletionStream creates a chat completion stream
func createChatCompletionStream(ctx *dgctx.DgContext, gcType string, templateName string, platformModel *PlatformModelPair, gcReq *GenerateContentRequest, bizParams map[string]any) (*openai.ChatCompletionStream, []any, error) {
	platform := platformModel.Platform
	model := platformModel.Model
	logParams := []any{templateNameKey, templateName, platformKey, platform, modelKey, model}
	if len(bizParams) > 0 {
		for k, v := range bizParams {
			logParams = append(logParams, k, v)
		}
	}
	dglogger.Infow(ctx, fmt.Sprintf("start to create stream %s", gcType), logParams...)
	client := platformClientMap[platform]
	ccReq := buildChatCompletionRequest(gcReq)
	ccReq.Model = model
	ccReq.Stream = true

	stream, err := client.CreateChatCompletionStream(context.Background(), ccReq)
	if err != nil {
		dglogger.Errorw(ctx, fmt.Sprintf("create stream %s error: %v", gcType, err), logParams...)
		if strings.Contains(err.Error(), "rate limit") {
			randomSeconds := utils.RandomIntInRange(5, 15)
			dglogger.Warnw(ctx, fmt.Sprintf("stream %s rate limit, sleepSeconds: %v", gcType, randomSeconds), logParams...)
			time.Sleep(time.Duration(randomSeconds) * time.Second)
		}
		return stream, logParams, err
	}

	return stream, logParams, nil
}

// receiveChatCompletionStream receives messages from a chat completion stream
func receiveChatCompletionStream(ctx *dgctx.DgContext, gcType string, stream *openai.ChatCompletionStream, respChan chan *GenerateContentResponse, platformModel *PlatformModelPair, gcReq *GenerateContentRequest, bizParams map[string]any, logParams []any) (*GenerateContentResponse, time.Duration, error) {
	start := time.Now()
	var respList []*GenerateContentResponse

	for {
		response, err := stream.Recv()
		if err != nil {
			if !errors.Is(err, io.EOF) {
				dglogger.Errorw(ctx, fmt.Sprintf("stream %s recv error: %v", gcType, err), logParams...)
			}
			break
		}

		gcResp := buildGenerateContentStreamResponse(gcReq, &response)
		respChan <- gcResp
		respList = append(respList, gcResp)

		if gcResp.FinishReason != "" {
			break
		}
	}

	mergedResp := mergeGenerateContentResponses(respList)
	mergedResp.Platform = platformModel.Platform
	mergedResp.Model = platformModel.Model

	if mergedResp.FunctionCall != nil && MyFunctionCallProvider != nil {
		var err error
		mergedResp.Content, err = MyFunctionCallProvider.Call(ctx, mergedResp.FunctionCall, bizParams)
		if err != nil {
			dglogger.Errorw(ctx, fmt.Sprintf("stream %s function call error: %v", gcType, err), logParams...)
			return mergedResp, time.Since(start), err
		} else {
			respChan <- mergedResp
		}
	}

	cost := time.Since(start)
	dglogger.Infow(ctx, fmt.Sprintf("stream %s success, cost: %v", gcType, cost), logParams...)

	return mergedResp, cost, nil
}

// getGenerateContentRequest gets the generate content request from a template generate content request
func getGenerateContentRequest(ctx *dgctx.DgContext, tgcReq *TemplateGenerateContentRequest) (string, *GenerateContentRequest, error) {
	templateName := tgcReq.TemplateName

	gcReq, err := MyAigcTemplateProvider.GetGenerateContentRequest(ctx, templateName, tgcReq.TemplateParams, tgcReq.MultiContent)
	if err != nil {
		return "", nil, err
	}

	if tgcReq.RequestId != "" {
		gcReq.RequestId = tgcReq.RequestId
	}

	fixGenerateContentRequest(gcReq)
	return templateName, gcReq, nil
}
